package project1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;


public class LexicalAnalyzer {

	public String[] Token_types = new String[10];
	public String state;
	public characters input = new characters();
	public ArrayList<Token> output;
	public static HashMap<String, Integer> states;
	public int line = 0, Tokenline = 0;
	enum States{START, STRING, STRING_APO,KEY_O_IDEN,KEYWORD,IDENTIFIER,SYMB_COLON,SYMB_COMMA,
		SYMB_LT,SYMB_GT,SYMB_EQ,SYMB_OPAREN,SYMB_CPAREN,SYMB_NOTEQ,SYMB_PER,SYMB_STAR,SYMB_QUES,
		SYMB_PLUS,SYMBOL,SYMB2_COLONL,SYMB2_LT_EQ,SYMB2_GT_EQ,SYMB2_NOT_EQ, COMMENT, BLOCK_COMMENT,
		COMMENT_OR_BLOCK, BLOCK_COMMENT_BAR}
	public static final Set<String> keywords = new HashSet<String>(Arrays.asList(
			new String[]{"Schemes","Facts","Rules","Queries"}));
	private static final Set<Character> symbols = new HashSet<Character>(Arrays.asList(
			new Character[]{':',',','<','>','=','?','(',')','!','.','?','*','+'}));
	
	public LexicalAnalyzer(characters input){
		this.input = input;
		this.output = new ArrayList<Token>();
	}
	public String ouputToString(){
		String tostring="";
		for(Token s: output){
			if (tostring.equals(""))
				tostring = s.toString();
			else 
				tostring = tostring+"\n"+s.toString();
		}
		tostring = tostring+"\nTotal Tokens = "+output.size();
		return tostring;
	}
	/*
	 * Finite State Machine used to take
	 * an input of type characters and convert it
	 * into a list of type Tokens.  Which contains
	 * Token type, line number, and value.
	 * 
	 * Returns True or False
	 */
	public boolean convert(){
		String value="";
		line = 1;
		boolean loop = true;
		States state = States.START;
		input.CurIndex = 0;
		while (loop == true){
			switch(state){
			case START:
				//If the character is a ' it means it is a string
				if(input.GetCurChar()=='\''){
					state = States.STRING;
					input.MoveNext();
					Tokenline = line;
					value = "'";
				} else
				//If it is a Keyword or an Identifier
				if(Character.isLetter(input.GetCurChar())){
					state=States.KEY_O_IDEN;
					value="";
				} else
				//Symbol Check
				if(symbols.contains(input.GetCurChar())){
					state=States.SYMBOL;
				}else
				//Skip New Lines	
				if (input.GetCurChar()=='\n'){
					input.MoveNext();
					line++;
				} else
				//Ignore White Space
				if(Character.isWhitespace(input.GetCurChar())){
					input.MoveNext();
				} else 
					//Skip New Lines	
				 if (input.GetCurChar()=='\r'){
					input.MoveNext();
				} else
				//Comments	
				if(input.GetCurChar()=='#'){
					state = States.COMMENT_OR_BLOCK;
					input.MoveNext();
				} else
				//If it has reached the EOF
				if(input.GetCurChar() == Character.MAX_VALUE){
					output.add(new Token("EOF","",line));
					loop=false;
				} else {
					output.add(new Token("UNDEFINED",Character.toString(input.GetCurChar()),line));
					input.MoveNext();
				}
				break;
			case STRING:
				//If a ' is found, could be end of a string or a double
				if(input.GetCurChar()=='\''){
					state = States.STRING_APO;
					input.MoveNext();
				} else if(Character.isDefined(input.GetCurChar())){
					if(input.GetCurChar()=='\n'){
						line++;
					} 
					value = value + input.GetCurChar();
					input.MoveNext();
				}else if (input.GetCurChar()==Character.MAX_VALUE){
					state=States.START;
					output.add(new Token("UNDEFINED",value,Tokenline));
					value="";
				}
				break;
			case STRING_APO:
				//If it finds another apostrophe then it is part of the string
				if(input.GetCurChar()=='\''){
					value = value + input.GetCurChar();
					input.MoveNext();
					state=States.STRING;
				}//If it is not an ' then save the string and continue
				else {
					value+="'";
					output.add(new Token("STRING",value,Tokenline));
					value="";
					state = States.START;
				}
				break;
			case KEY_O_IDEN:
				if(Character.isLetterOrDigit(input.GetCurChar())){
					value = value+input.GetCurChar();
					input.MoveNext();
				} 
				//If we find a space it means it is the end of either this Keyword or Identifier
				else if(!Character.isLetterOrDigit(input.GetCurChar())){
					if(keywords.contains(value)){
						state=States.KEYWORD;
					} else {
						state = States.IDENTIFIER;
					}
				} else {
					//There is an error an a incorrect character was found
					System.out.println("Incorrect Identifier");
					return false;
				}
				break;
			case KEYWORD:
				output.add(new Token(value.toUpperCase(),value,line));
				value="";
				state = States.START;
				break;
			case IDENTIFIER:
				output.add(new Token("ID",value,line));
				value="";
				state = States.START;
				break;
			case COMMENT_OR_BLOCK:
				if (input.GetCurChar()=='|'){
					state = States.BLOCK_COMMENT;
					input.MoveNext();
					Tokenline = line;
				}else 
					state = States.COMMENT;
				break;
			case BLOCK_COMMENT:
				if(input.GetCurChar()=='\n')
					line++;
				if(input.GetCurChar()=='|'){
					state = States.BLOCK_COMMENT_BAR;
					input.MoveNext();
				} else if(input.GetCurChar()==Character.MAX_VALUE){
					output.add(new Token("UNDEFINED",value,Tokenline));
					value="";
					state = States.START;
				} else if(input.GetCurChar()=='\r'){
					input.MoveNext();
				}else {
					value = value + input.GetCurChar();
					input.MoveNext();
				}
				break;
			case BLOCK_COMMENT_BAR:
				if(input.GetCurChar()=='#'){
					output.add(new Token("COMMENT",value,Tokenline));
					input.MoveNext();
					value ="";
					state = States.START;
				} else {
					value=value+'|';
					state=States.BLOCK_COMMENT;
				}
				break;
			case COMMENT:
				if(input.GetCurChar()=='\n'){
					output.add(new Token("COMMENT",value,line));
					value="";
					line++;
					input.MoveNext();
					state = States.START;
				} else if(input.GetCurChar()=='\r'){
					input.MoveNext();
				} else {
					value = value + input.CurChar;
					input.MoveNext();
				}
				break;
			case SYMBOL:
				if(input.GetCurChar()==':')
					state=States.SYMB_COLON;
				else if(input.GetCurChar()==',')
					state=States.SYMB_COMMA;
				else if(input.GetCurChar()=='+')
					state=States.SYMB_PLUS;
				else if(input.GetCurChar()=='<')
					state=States.SYMB_LT;
				else if(input.GetCurChar()=='>')
					state=States.SYMB_GT;
				else if(input.GetCurChar()=='=')
					state=States.SYMB_EQ;
				else if(input.GetCurChar()=='(')
					state=States.SYMB_OPAREN;
				else if(input.GetCurChar()==')')
					state=States.SYMB_CPAREN;
				else if(input.GetCurChar()=='!')
					state=States.SYMB_NOTEQ;
				else if(input.GetCurChar()=='.')
					state=States.SYMB_PER;
				else if(input.GetCurChar()=='*')
					state=States.SYMB_STAR;
				else if(input.GetCurChar()=='?')
					state=States.SYMB_QUES;
				input.MoveNext();
				break;
			case SYMB_COLON:
				if(input.GetCurChar()=='-'){
					state=States.SYMB2_COLONL;
					input.MoveNext();
				} else {
					output.add(new Token("COLON",":",line));
					state = States.START;
				}
				break;
			case SYMB_LT:
				if(input.GetCurChar()=='='){
					state=States.SYMB2_LT_EQ;
					input.MoveNext();
				} else {
					output.add(new Token("LT","<",line));
					state = States.START;
				}
				break;
			case SYMB_GT:
				if(input.GetCurChar()=='='){
					state=States.SYMB2_GT_EQ;
					input.MoveNext();
				} else {
					output.add(new Token("GT",">",line));
					state = States.START;
				}
				break;
			case SYMB_NOTEQ:
				if(input.GetCurChar()=='='){
					state=States.SYMB2_NOT_EQ;
					input.MoveNext();
				} else {
					output.add(new Token("UNDEFINED","!",line));
					state = States.START;
				}
				break;
			case SYMB_COMMA:
				output.add(new Token("COMMA",",",line));
				state = States.START;
				break;
			case SYMB_EQ:
				output.add(new Token("EQ","=",line));
				state = States.START;
				break;
			case SYMB_OPAREN:
				output.add(new Token("LEFT_PAREN","(",line));
				state = States.START;
				break;
			case SYMB_PER:
				output.add(new Token("PERIOD",".",line));
				state = States.START;
				break;
			case SYMB_CPAREN:
				output.add(new Token("RIGHT_PAREN",")",line));
				state = States.START;
				break;
			case SYMB_STAR:
				output.add(new Token("MULTIPLY","*",line));
				state = States.START;
				break;
			case SYMB_QUES:
				output.add(new Token("Q_MARK","?",line));
				state = States.START;
				break;
			case SYMB_PLUS:
				output.add(new Token("ADD","+",line));
				state = States.START;
				break;
			case SYMB2_COLONL:
				output.add(new Token("COLON_DASH",":-",line));
				state = States.START;
				break;
			case SYMB2_LT_EQ:
				output.add(new Token("LE","<=",line));
				state = States.START;
				break;
			case SYMB2_GT_EQ:
				output.add(new Token("GE",">=",line));
				state = States.START;
				break;
			case SYMB2_NOT_EQ:
				output.add(new Token("NE","!=",line));
				state = States.START;
				break;
			
				
			}
		}
		
		return true;
	}

}
